#!/usr/bin/env python

import pyphon, csv, sys, pickle, re, os
from pprint import pprint
from optparse import OptionParser

usage = \
"""pyphon_maketableaux.py modelfile inputfile <opt> [outputfile [separator]]

read a PyPhon model and list of inputs and write the tableaux of contender
outputs to disk.

<opt> must be supplied as one of "OT", "HG", or "pareto"; it indicates whether
the candidates in the tableaux should be OT-contenders, HG-contenders, or
Pareto-contenders.

If outputFile is omitted, it defaults to modelfile-inputfile-opt.csv.

Separator, if given, should be a string that will be used to separate output
forms that are not distinguished by the model. If not given, it defaults to 
" | "."""

def printUsage():
    print usage

def parseDie(file, lineno, msg):
    print "Failed to read model description file %r (line %d): %s" % (file,
            lineno, msg)
    raise SystemExit

def openWithExtension(basename, extension, mode):
    suffix = '.'+extension
    if basename.endswith(suffix):
        try:
            return open(basename, mode)
        except IOError:
            pass
    try:
        return open(basename+suffix, mode)
    except IOError:
        pass

    try:
        return open(basename, mode)
    except IOError:
        raise ValueError("Unable to open %r or %r for mode %r" %
                (basename+suffix, basename, mode))

def filenameWithExtension(basename, extension):
    suffix = '.'+extension
    if basename.endswith(suffix):
        return basename
    return basename + suffix

def escapeBackslashes(input):
    def sub(match):
        return match.group(1)
    return re.sub(r'\\(.)', sub, input)

def autoCreateModel(modelFile):
    print 'Model file %s does not exist.' % (modelFile,)
    descriptionFile = modelFile.replace('.mod', '')
    
    print 'Attempting to auto-generate...'
    try:
        import pyphon_makemodel
    except ImportError:
        myPath = os.path.split(__file__)[0]
        if myPath not in sys.path:
            sys.path.append(myPath)
        try:
            import pyphon_makemodel
        except ImportError:
            print 'Auto-generation failed. Unable to import pyphon_makemodel.'
            return False

    # let exceptions propagate
    pyphon_makemodel.main(descriptionFile, outputFile=modelFile)

    if os.path.exists(modelFile):
        print 'Auto-generation successful.'
        return True
    else:
        print 'Auto-generation failed.'
        return False

def main(modelFile, inputFile, opt, outputFile=None, separator=" | "):
    modelFile = filenameWithExtension(modelFile, 'mod')
    if not os.path.exists(modelFile):
        if not autoCreateModel(modelFile):
            print 'Aborting pyphon_maketableaux: model file %s not found '\
                  'and unable to auto-generate.' % modelFile
            raise SystemExit

    modelBasename = modelFile.replace('.mod', '')
    inputBasename = inputFile.replace('.csv', '')
    outputFile = outputFile or '%s-%s-%s' % (modelBasename, inputBasename, opt)
    print 'Loading EVAL from %s...' % (modelFile,)
    eval, ipaFeatures, ipaDiacriticFeatures, em = pickle.load(open(modelFile,
        'r'))
    print 'Done (%d states).' % len(eval.nodes())
    #pprint(ipaFeatures['l'])
    reader = csv.reader(openWithExtension(inputFile, 'csv', 'rU'))
    contenderCosts = None
    opt = opt.lower()
    if opt in ("ot", "OT"):
        contenderCosts = pyphon.pycbg.contenderCostsOT
    elif opt in ("hg", "HG"):
        contenderCosts = pyphon.pycbg.contenderCostsHG
    else:
        print 'Error: unknown optimization function: %r' % opt
        return

    if opt in em:
        print 'Restoring saved EVAL matrices.'
        evalMatrices = em[opt]
    else:
        print 'Building EVAL matrices...'
        evalMatrices = pyphon.pycbg.makeEvalMatrices(eval, contenderCosts,
                progress=True)
        print 'Done. Saving EVAL matrices.'
        em[opt] = evalMatrices
        pickle.dump((eval, ipaFeatures, ipaDiacriticFeatures, em), 
                open(modelFile, 'w'))

    inputs = []
    lineno = 0
    for row in reader:
        lineno += 1
        if (not row) or row[0].startswith('#'):
            continue

        input = escapeBackslashes(row[0].strip())
        inputs.append(pyphon.Form(input, ipaFeatures, ipaDiacriticFeatures))
        #print 'Features of symbol %s: %r' % (inputs[-1][1], inputs[-1][1].features)

    print '%d inputs.' % len(inputs)

    f = openWithExtension(outputFile, 'csv', 'w')
    writer = csv.writer(f)
    for input in inputs:
        contenders = pyphon.pycbg.contendersGeneric(eval, input, contenderCosts,
                evalMatrices)
        print 'Input %s: %d %s-contender outputs.' % (input, len(contenders),
                opt)

        # input, output, winner, con1, con2, ...
        if not contenders:
            print 'No tableau written for input %s: no contender outputs.' %\
                input
            continue
        writer.writerow(['','',''] + [name for name,viols in contenders[0][0]])
        for weights, outputs in contenders:
            writer.writerow([input, separator.join([str(o) for o in outputs]),''] + \
                    [viols for name,viols in weights])
        writer.writerow(())

    print 'Wrote tableaux to %r' % (f.name,)
    f.close()


if __name__ == "__main__":
    parser = OptionParser(usage=usage)
    parser.add_option('-V', '--version', action='store_true', dest='version',
            default=False,
            help='Print pyPhon version and quit.')
    p_options, args = parser.parse_args()

    if p_options.version:
        print 'pyPhon version %s.' % pyphon.__version__
        raise SystemExit

    if len(args) < 3:
        printUsage()
    elif len(args) < 4:
        main(args[0], args[1], args[2])
    elif len(args) < 5:
        main(args[0], args[1], args[2], args[3])
    elif len(args) < 6:
        main(args[0], args[1], args[2], args[3], args[4])
    else:
        printUsage()

